
#include <mpi.h>
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <math.h>
#include <stdio.h>
#include <sys/time.h>
//#include <omp.h>

using namespace std;
// contadores de palavras e frases palindromas
int n_pali_word = 0;
int n_pali_phrase = 0;


string toUpperCase(string s) {
  for (int i = 0; i < s.size(); i++)
    s[i]=toupper(s[i]);
  return s;
}

// Limpa a string de apontuacao
void remove_pontuacion(string& in)
{
    for(int i=0; i <in.length(); i++)
    {
        if( in[i] != '.' && in[i] != '!' && in[i] != '?' && ispunct(in[i]))
          in[i]= ' ';
    }
    return;
}


// verifica se um string é palindromo;
bool palindrome(string s) {
  if( equal(s.begin(), s.begin() + s.size()/2, s.rbegin()) )
      return true;
  else
      return false;
}


string pop_back(string& in) {
  string out = in;
  if( ispunct(in[in.size()-1]) ) {
    out = in.substr(0, in.size()-1);
  }
  return out;
}

char back(string& in) {
  return in[in.size()-1];
}

// Pega a linha do aquivo e quebra em palavras e junta novamentes em uma frase
void token(string& in) {
    istringstream iss(in);
    string phrase;

    do
    {
        string sub;
        string aux;
        iss >> aux;
        
        aux = toUpperCase(aux);
        sub = pop_back(aux);
        if( sub.size() >2 && palindrome(sub) )  // Verifica se a palavra é palindromo 
            n_pali_word++;

        if( back(aux) != '.' && back(aux) != '!' && back(aux) != '?' && iss) {
          phrase += sub;
        }
        else {
          
          phrase += sub;

          //cout << phrase << endl;
          if(phrase.size() > 2 && palindrome(phrase) ) // verifica se a frase eh palindromo 
            n_pali_phrase++; 
  
          phrase.clear();
        }
    } while (iss);
}

int main (int argc, char ** argv){

  // Variavei para contagem de operações local
  int local_pali_word = 0;
  int local_pali_phrase = 0; 

  //contagem do tempo
  double       inittime,totaltime;
  

  //Variaveis auxiliares 
  int numberNodes, myNode, numLines = 0,i=1;
  int numPalindrome = 0, numPrime = 0, bufInt[2];
  vector<string *> text;
  unsigned int * num_msg;


  //Para comunicação MPI
  MPI::Status status;


  //Inicia MPI
  MPI::Init(argc, argv);

  //inicia contagem do tempo 
  inittime = MPI::Wtime();

  //pega o numero de processos que estão sendo executados
  numberNodes = MPI::COMM_WORLD.Get_size();

  //pega o numero do processo [0..n]
  myNode = MPI::COMM_WORLD.Get_rank();

  
    if( myNode == 0 ){
      ifstream fin ("shakespe.txt"); // ifstream = padrão ios:in
      if (fin.is_open())
      { 
        num_msg = (unsigned int*)calloc(sizeof(unsigned int) , numberNodes);
       
        i=1;
        //Carrega todo o arquivo em um vector de linhas do arquivo
        //Enquanto isso conta em um vetor quantas msg serão enviadas para cada processo
        while ( !fin.eof() )
        { //enquanto end of file for false continua
          string *line;
          line = new string();
          getline(fin, *line, '\r'); // pega a linha do arquivo
          numLines++;
          if( i >= numberNodes)
            i=1;
          text.push_back(line);
          num_msg[i]++;
          i++;
        }
        i=1;

        //Envia o numero de linhas que cada processo ira receber pra processar  
        while(i<numberNodes){
          MPI::COMM_WORLD.Send(&(num_msg[i]), 1,MPI::INT, i, 1);
          i++;
        }
        

        i=1;
        //Envia as mensagens aos processos cada mensagem é uma linha do texto 
        //que o processo deve processar 
        while(!text.empty()){
          string *line;
          line = text.back();
          text.pop_back();
          if( i >= numberNodes)
            i=1;
          MPI::COMM_WORLD.Send(line->c_str(), line->size() +1,MPI::CHAR, i, 2);
          
          //std::cout << *line << std::endl<<  i << std::endl;
          i++;
          delete line; 
        }
       
        free(num_msg);
      }else{
        return(-1);
      }
    }else{
      
      
      num_msg = (unsigned int*)malloc(sizeof(unsigned int) * 1);
      *num_msg=0;
      MPI::COMM_WORLD.Recv(num_msg, 1, MPI::INT, 0, 1, status);
      //std::cout << "Process " << myNode << " of " << numberNodes << " received " << (*num_msg) << std::endl;
     
      
      
      i=0;
      while( i < (*num_msg) )
      {
        

        int number_amount, flag;

        //pega o status da conecção 
        MPI::COMM_WORLD.Probe(0, 2 ,status) ;
        
       
        // Descobre o tamanho da  msg a ser recebida
        number_amount = status.Get_count(MPI::CHAR);
        
 
        // Aloca memoria para a linha 
        char* buf = (char*)malloc(sizeof(char) * number_amount);
        
        // Recebe uma linha para ser processada
        MPI::COMM_WORLD.Recv(buf, number_amount, MPI::CHAR, 0, 2, status);
        string line(buf);


        //Processa a linha
        remove_pontuacion(line);


              istringstream iss(line);
              string phrase;
              do
              {
                string sub;
                string aux;
                iss >> aux;
                
                aux = toUpperCase(aux);
                sub = pop_back(aux);
                if( sub.size() >2 && palindrome(sub) ){  // Verifica se a palavra é palindromo 
                    n_pali_word++;local_pali_word++;
                }
                if( back(aux) != '.' && back(aux) != '!' && back(aux) != '?' && iss) {
                  phrase += sub;
                }
                else {
                  
                  phrase += sub;

                  //cout << phrase << endl;
                  if(phrase.size() > 2 && palindrome(phrase) ){ // verifica se a frase eh palindromo 
                    n_pali_phrase++;local_pali_phrase++; 
                  } 
                  phrase.clear();
                }
              } while (iss);

              line.clear();
        
        
        free(buf);

        i++;
      }
      char name[60];
      int resultlen;
      MPI::Get_processor_name(name, resultlen);
      cout << "Task "<< name << " Results: " << "number of words palindrome: " << local_pali_word << endl;
      cout << "Task "<< name << " Results: " << "number of phrases palindrome: " << local_pali_phrase << endl;
      free(num_msg);
    }
  //Reduções
  MPI::COMM_WORLD.Reduce(&local_pali_word,&n_pali_word,1,MPI::INT,MPI::SUM,0);
  MPI::COMM_WORLD.Reduce(&local_pali_phrase,&n_pali_phrase,1,MPI::INT,MPI::SUM,0);
  totaltime = MPI::Wtime() - inittime;
  if( myNode == 0 ){
      cout << "Total Results: " << "number of words palindrome: " << n_pali_word << endl;
      cout << "Total Results: " << "number of phrases palindrome: " << n_pali_phrase << endl;
      cout << "Total Time: "<< totaltime << endl;
  }
  MPI::Finalize();
}
